#!/bin/bash
#THIS DRIVER WRITTEN BY MATTHEW VONA ARB FOR THE SABIO4010
#FOR QUESTIONS WRITE mailto:mvona@arb.ca.gov
#UPDATE: 7/28/2009; Added data file lock handler; Corrected 
#UPDATE: 04/24/2012; Added Box Temp reporting, added Box Temp Diagnostics, added relative directory placement
#IZS variable handeling in awk diag() routine.  
#-------------------------------------------------------------
#!/usr/bin/env bash
#COOL DOWN PERIOD (CDP) IS THE NUMBER OF MINUTES
#THIS IS THE RELATIVE DIRECTORY VARIABLE 
DIR="$( cd -P "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
#TO INVALIDATE FOR RECOVERY 
# THIS IS THE FUNCTION CALLED IN THE EVENT THAT CTRL+C IS PRESSED
#leave debug=1 if you want debug messages
debug=1
#ROUND FUNCTION
round()
{
	python -c "print int(round($1))"
}
cleanup()
{
    echo "Caught Signal ... cleaning up."
    if !(rm -f Data/.locked > /dev/null); then
      {
                                    exit 1
      }
    fi
   	for i in `pgrep -P ${plotterid}`;
    	do
   				kill -9 ${i} 2>/dev/null
   		done
    kill -9 ${plotterid} 2>/dev/null
    echo "Done cleanup ... quitting."
    exit 0
 }
                                                                         					    	        
###############################################################
# BEGIN DISPLAY FUNCTION : MJV 1/2/2013						  #
###############################################################
#########################################################################################
#                       BEGIN MAIN DISPLAY FUNCTION.                                    #
#########################################################################################
#read config file
while read line; do
    eval $line
    done < Config/config
    if [[ -z "$no_plots" ]]; then
        {
                bash ${DIR}/plotter.sh 1>/dev/null 2>/dev/null &
                plotterid="$!"
         }
     fi
#THE DIAGNOSTIC DATA (DIAG) ROUTINE TAKES THE DATA STREAM FROM THE Sabio 4010
#AND TESTS FOR THE RECOGNITION OF KEY WORDS IN EACH LINE OF OUTPUT.
#IF THE KEY WORD IS DETECTED ON THAT LINE, A TEST IS PERFORMED. 
#IF THE TEST SUGGESTS THAT AN ERROR CONDITION EXISTS BASED UPON THE ALLOWABLE
#RANGES SPECIFIED BY THE ARB CHECKSHEET, THE OFFENDING LINE IS WRITTEN OUT 
#TO A FILE IN THE Data DIRECTORY. THE NAME OF THE DESTINATION FILE IS
#errors{HH}.
#'{ print "Dilluent_Set="$1" Dilluent_Flow="$2" Ozone_Set="$3" Ozone_Flow="$4" Source_Set="$5" Source_Flow="$6" Source_Flow="$7" Sys_Temp="$8" Dil_Valves_On="$9+0 }'`
#labeled_ozone=`echo "${ozone}" | sed 's/,/ /g' | gawk '{ print "O3_Tmp_Set="$1" O3_Tmp_Mon="$2" O3_Lamp_Set="$3" V O3_Lamp_Curr="$4" O3_Lamp_Intensity="$5" O3_Conc_SetPoint="$6" PPB Calculated_Ozone_Conc="$7" PPB O3_Diff="$6-$7" PPB" }'`
#labeled_photo=`echo "${photo}" | sed 's/,/ /g' | gawk '{ print "O3_Avg_meas="$1" Photo_Lamp_Temp_Set="$2" Photo_Lamp_Meas="$3" Photo_Lamp_Set="$4" V Photo_Lamp_Curr="$5" Photo_Lamp_Intensity="$6" gas_temp="$9" gas_press="$10" gas_flow="$11 }'`

diag()
{
#NEED TO ADD A SCREEN TO DISREGARD LOW OZONE FLOW WHEN OZONE NOT FLOWING
this_name=$(cat ${DIR}/.usr$instance | grep -o "\-r [A-Za-z0-9]*" | awk '{ print $2 }')
thishour=`date "+%H"`
thisalert="errors$thishour"
awk -v name=$this_name -v stamp=$timestamp -v composite=$composite_demand '
	BEGIN { RS=" "} ;
	{
	if (/Dilluent_Flow=/) { 
		split($0,a,"="); { if (10-a[2] > .99 && composite+0 > 1) print stamp" " name " CARBLogger detected that the zero air flow was less than 9.9 lpm: " a[2] } 
		};
	#if (/Samp_Flow=/) { 
	#	split($0,a,"="); { if (a[2] < 500 && composite+0 > 1) print stamp" " name " CARBLogger detected that the sample flow was less than 500 cc : " a[2] } 
	#	};
	#if (/Samp_Temp=/) { 
	#	split($0,a,"="); { (if a[2] < 5 || a[2] > 40) && (composite+0 > 1) print stamp" " name " CARBLogger detected that the sample temp was less than 5 or more than 40 C : " a[2] } 
	#	};
	#if (/Ozone_Flow=/) { 
	#			split($0,a,"="); { if (a[2] >= 101 || a[2] <= 99 && length(a[2]) != 0 && composite > 0 ) print stamp" "name" has " a[1] " out of range during an active state:" a[2] }
	#	};
	#if (/O3_Tmp_Mon=/) { 
	#	split($0,a,"="); { if (a[2] >= 50.5 || a[2] <= 49.5 ) print stamp" " name " has Ozone Lamp Temp more than 50.5 and less than 49.5 : " a[2] } 
	#	};
	};		
	'>>Data/${thisalert}			
}
#BEGIN WRITE
#THIS FUNCTION HANDELS READ RIGHT HANDSHAKES BETWEEN VARIOUS DRIVERS
#IN GENERAL, THE DATA IS PLACED IN THE VARIABLE $TIMESTAMP PRIOR TO CALL
#NEXT, IT OPENS THE DATA DIRECTORY AND LOOKS FOR THE .locked FILE
#IF IT EXISTS, IT HANGS OUT UNTIL IT DOESN'T EXIST. AT THAT POINT
#IT CREATES A .locked FILE, AND APPENDS TO THE DATA RECORD.
#FINALLY IT REMOVES THE .locked FILE IT HAS CREATED.
#THIS FUNCTION ALSO PARSES THE .DISABLED FILE IN THE DRIVER FOLDER
#AND TAKES A CHANNEL OFF LINE BY PREPENDING --DISABLED-- TO EACH LINE OF CONTENT IN THE DATA DIRECTORY
#written by MJV
write()
{
#CONFIRM THAT THERE IS NO HIDDEN FILE CALLED '.locked'
	while [ -e "Data/.locked" ];
		do
			sleep .2;
		done	
	echo "LOCKED" > "Data/.locked";
	#PARSE THE .disabled File
	channel_status=`cat "${DIR}/.disabled${instance}"`
	
	#MAKES SURE A VALUE OF 0 IS USED
	if [ `expr $channel_status + 0` -eq 1 ]; then
		if !(echo "$data" | sed 's/^/'$timestamp'/g'| sed 's/$/'\-\-DISABLED\-\-'/g'| tee -a "Data/$datafile" | diag); then
			{
			echo "GENERAL FAILURE WRITING TO RECORD: $#";
			cleanup;
			}
		fi
	else
		if !(echo "$data" | sed 's/^/'$timestamp'/g'| tee -a "Data/$datafile" | diag); then
			{
			echo "GENERAL FAILURE WRITING TO RECORD: $#";
			cleanup;
			}
		fi
	
	fi
	if !(rm -f "Data/.locked" > /dev/null); then
		{
			echo "GENERAL FAILURE REMOVING LOCK: $#";
			exit 1;	
		}
	fi
	if !(timestamp="" && data=""); then
		{
		echo "GENERAL FAILURE CLEARING WRITE BUFFER: $#";
		cleanup;
		}
	fi	
}

#THIS IS THE ACTUAL COMMUNICATION WITH THE INSTRUMENT
#THE TIMER CONTROL. AT THE BEGINING IT TESTS FOR THE 
#EXISTENCE IF TOGGLE VARRIABLES WHICH ARE UTILIZED BOTH BY
#THE COMM ROUTINE runit() AND THE DIAGNOSTICS DATA ROUTINE
#diag()
runit()
{
#IF THE -d, -C, or -p variable is set to on, then append those parameters
#to the sjinn communication string. If the -i parameter is passed, diag will
#test for IZS specific routines.
if [ "$izs_val" = "on" ]; then
	izs="on"
fi
if [ "$dtr_val" = "on" ]; then
 	dtr="--dtr" 
fi
if [ "$ctr_val" = "on" ]; then
 	ctr="--ctr"
fi
if [ "$p8n1_val" = "on" ]; then
 	p8n1="-p8n1"
fi
#Date stamp
today=`date "+%Y-%d-%m"`
timestamp=$rprt_nm::`date "+%m-%d-%Y_%H:%M"`:::
#THIS IS THE NAME OF THE DATA FILE RIGHT NOW
datafile="$saroad-$today"
#TALK TO THE INSTRUMENT, PRE-APPEND THE TIMESTAMP AND REPORTING NAME TO EACH
#IF IT EXISTS, IT HANGS OUT UNTIL IT DOESN'T EXIST. AT THAT POINT
###################################################
#SEQUENCE OBTAINS THE SEQUENC AND STEP INFORMATION#
###################################################
#SEQUENCE TAKES THE SPECIAL 2 DIGIT SEQUENCE OUTPUT AND STEP PROVIDED BY
#THE SABIO.	
sequence=`timeout $timeout_time sjinn -s '\\h1B1B40\\hGS,00,S\\r' -b $bd_rt -d $dvc_prt -r$rsp_lngth -w$wt_rsp1 | grep \,$ `
if [[ ${debug} -eq 1 ]]; then
{
	echo "My sequence is $sequence";

}
fi
#TRANSLATE THE SABIO OUTPUT INTO A FORMAT CARBLOGGER CAN USE
sequence_cal=`echo "$sequence" | awk 'BEGIN { FS =","}; { print $1*10+$2}' `
if [[ ${debug} -eq 1 ]]; then
{
	echo "My sequence cal is ${sequence_cal}";
}
fi
######################################
#SOLENOID OBTAINS THE MFC INFORMATION#
######################################
#SOLENOID TAKES THE FLOW RATES OF THE DILUENT 
#(ZERO AIR), OZONE, and SOURCE MASS FLOW CONTROLLERS
#.
solenoid=`timeout $timeout_time sjinn -s '\\h1B1B40\\hGS,00,D\\r' -b $bd_rt -d $dvc_prt -r$rsp_lngth -w$wt_rsp1 | grep \,$`		
if [ -z ${solenoid} ]; then
{
	solenoid='***SOLENOID DISCONNECT***'	
}
fi
if [[ ${debug} -eq 1 ]]; then
{
	echo "My solenoid is ${solenoid}";
}
fi
#FROM THE OUTPUT RETURNED BY THE SABIO, EXTRACT THE PORTION WHICH TELLS US IF
#ZERO AIR HAS BEEN TURNED ON. THIS IS USED LATER TO FLAG THE DATA INVALID.
solenoid_cal=`echo ${solenoid} | sed 's/,/ /g' | awk '{ print $1 + 0}'`
if [[ ${debug} -eq 1 ]]; then
{
	echo "My solenoid cal is ${solenoid_cal}";
}
fi
###############################################
#OZONE OBTAINS THE OZONE GENERATOR INFORMATION#
###############################################
ozone=`timeout $timeout_time sjinn -s '\\h1B1B40\\hGS,00,O\\r' -b $bd_rt -d $dvc_prt -r$rsp_lngth -w$wt_rsp1 | grep ',$'`
if [[ ${debug} -eq 1 ]]; then
{
	echo "My ozone is ${ozone}";
}
fi
#RETURN A MESSAGE INDICATING THAT NO SERIAL SIGNAL WAS RECEIVED FROM THE OZONE QUERY
if [[ -z ${ozone} ]]; then
{
	ozone='***OZONE DISCONNECT***'	
}
fi
#EXTRACT THE INFORMATION NEEDED TO DETERMINE IF THE OZONE GENERATOR WAS ON OR OFF
ozone_cal=`echo $ozone | sed 's/,/ /g' |awk '{ print $4 }'`
if [[ ${debug} -eq 1 ]]; then
{
	echo "My ozone cal point is ${ozone_cal}";
}
fi
#################################################
#PHOTO OBTAINS THE OZONE PHOTOMOETER INFORMATION#
#################################################
photo=`timeout $timeout_time sjinn -s '\\h1B1B40\\hGS,00,P\\r' -b $bd_rt -d $dvc_prt -r$rsp_lngth -w$wt_rsp1 | grep \,$`
photo_cal=`echo $photo | sed 's/,/ /g' |awk '{ print $4 }'`
if [[ ${debug} -eq 1 ]]; then
{
	echo "My photometer is ${photo}";
}
fi
#RETURN A MESSAGE INDICATING THAT NO SERIAL SIGNAL WAS RECEIVED FROM THE OZONE QUERY
if [[ -z ${photo} ]]; then
{
	ozone='***PHOTOMETER DISCONNECT***'	
}
fi
#R Smith asked for the driver to flag invalid any data where an operator was
#performing a manual calibration. We create a variable "composite_demand"
#which is the arithmetic product of Zero Air Flow, Gas Flow,and Ozone
#Lamp current. If this value is greater than 0, set the cal= value to 99.
#The DMS.CFG table is given a virtual parameter "manual_cal- man_cal 99 -- -- 0 1
#for every manafold of system. This will cause any instrument running
#on any system to receive an invalid op code (9) shoul cal step 99
#be present. Conversely, if this line is NOT placed in the dms.cfg file,
#a data gap in DMS will be observed. -- M Vona
#SEE IF ANYONE IS ASKING FOR ANYTHING MANUALY
composite_demand=`echo ${solenoid} | sed 's/,/ /g' | awk '{ print $1+$5+0 }'`
composite_demand=`echo ${ozone} | sed 's/,/ /g' | awk -v old_comp=${composite_demand} '{ print $3+old_comp+0 }'`
#LABEL THE DATA STREAMS FOR THE RAW DATA FILE AND DIAGNOSTICS
labeled_solenoid=`echo "${solenoid}" | sed 's/,/ /g' | awk '{ print "Dilluent_Set="$1" Dilluent_Flow="$2" Ozone_Set="$3" Ozone_Flow="$4" Gas_Flow_Ctrl="$5" Gas_Flow_Mon="$6" Source_Flow_A="$7" Sys_Temp="$8" Dil_Valves_On="$9+0 }'`
labeled_ozone=`echo "${ozone}" | sed 's/,/ /g' | gawk '{ print "O3_Tmp_Set="$1" O3_Tmp_Mon="$2" O3_Lamp_Ctrl="$3" O3_Lamp_Mon="$4" O3_Lamp_Intensity="$5" O3_Conc_Ctrl="$6" PPB O3_Conc_Mon="$7" PPB O3_Diff="$6-$7" PPB" }'`
labeled_photo=`echo "${photo}" | sed 's/,/ /g' | gawk '{ print "Photo_O3_conc="$1" Photo_Lamp_Temp_Set="$2" Photo_Lamp_Meas="$3" Photo_Lamp_Set="$4" V Photo_Lamp_Curr="$5" Photo_Lamp_Intensity="$6" Samp_Temp="$9" Samp_Press="$10" Samp_Flow="$11 }'`
#CREATE DIAGNOSTIC VALUES
O3_diff=`echo $labeled_ozone | grep -o O3_diff=[+,-,.,0-9]*`
#GRAB PRVIOUS CAL AND EQUILIBRIUM STATES
my_last_cal=`cat ${DIR}/.my_last_cal`
my_last_equi=`cat ${DIR}/.my_equi`
if [[ ${sequence_cal} -eq 0 ]]; then
			{
				#export a global variable to be used for the driver to determine its last non-valid state
				#BECAUSE THE CURRENT CAL STATE IS "0" WE CHECK FOR
				#A PREVIOUS CAL STATE OTHER THAN 0
				#IF THE CAL STATE IS A NUMBER <10, WE SET IT TO 
				#THE LAST CAL STATE MINUS 1, BECAUSE WE ARE COUNTING DOWN
				#IF IT IS > 10, IT WAS A CAL SEQ, SO WE SET IT TO THE COOLDOWN PERIOD
				
				
				if [ ${my_last_cal} -lt 10 ]; then
				{ 
						#BEGIN COUNTDOWN LOGIC
						if [ ${my_last_cal} -eq 0 ]; then 
							{
						
								#env my_last_cal=0
								echo -n "0" > ${DIR}/.my_last_cal
								sequence_cal="0"
							}
						else
							{
								#env my_last_cal=`echo ${my_last_cal}-1 | bc`
								echo -n `echo ${my_last_cal}-1 | bc` > ${DIR}/.my_last_cal					
								sequence_cal="66"
							}
						fi
					}
					else
					{
						
						#env my_last_cal=${cdp}
						echo -n "$recov" > ${DIR}/.my_last_cal
						sequence_cal="66"					
					}
				fi
				#Per R. Smith, if anyone initiates any manual cal, this data should be marked invalid.
				#However, this should be exempt from recovery and equilibration calculations.
				if [ `round ${composite_demand}` -ne 0 ]; then
				{
					sequence_cal="66"
				} 
				fi
				
				data="SEQUENCE: ${sequence} DILLUENT: ${labeled_solenoid} OZONE: ${labeled_ozone} PHOTOMETER:${labeled_photo} composite_demand=${composite_demand} cal=${sequence_cal}"
	
			}
		else
			{
			
			if [[ ${my_last_cal} -ne ${sequence_cal} ]]; then
				{
					echo -n "$equi" > ${DIR}/.my_equi	
					echo -n "$sequence_cal" > ${DIR}/.my_last_cal
					data="SEQUENCE:${sequence} DILLUENT:${labeled_solenoid} OZONE:${labeled_ozone} PHOTOMETER:${labeled_photo} composite_demand=${composite_demand} cal=66"				
				}
			elif [[ ${my_last_equi} -gt 0 ]]; then
				{
					echo -n `echo ${my_last_equi}-1 | bc` > ${DIR}/.my_equi
					echo -n "$sequence_cal" > ${DIR}/.my_last_cal
					data="SEQUENCE:${sequence} DILLUENT:${labeled_solenoid} OZONE:${labeled_ozone} PHOTOMETER:${labeled_photo} composite_demand=${composite_demand} cal=66"
				
				}
			else
				{
				echo -n "$sequence_cal" > ${DIR}/.my_last_cal
				#env my_last_cal="${sequence_cal}"
				data="SEQUENCE:${sequence} DILLUENT:${labeled_solenoid} OZONE:${labeled_ozone} PHOTOMETER:${labeled_photo} composite_demand=${composite_demand} cal=${sequence_cal}"
				}
			fi
			}
fi
	
write
}
#THIS IS THE FUNCTION RUN IN THE EVENT THAT THE REQUIRED PARAMETERS
#ARE NOT ENTERED.
usage()
{
clear
cat << EOF
usage: $0 options
OPTIONS:
-h	display help
-n	the device identifier
-r	rprt_nm [rn]
-d	dvc_prt [eg, /dev/ttyPS0, /dev/ttyUSB0] 
-b	bd_rt [2400,4800,9600,etc.]
-w	wt_rsp1 [seconds]
-l	rsp_lngth [characters]
-s	SAROAD number of the station
-t	[on/off] (OPTIONAL)
-C	[on/off] (OPTIONAL)
-p	[on/off] (OPTIONAL)
-R  Number of minutes to recover from a cal step
-E	Number of minutes to neglect at the begining of each cal step.
Last updated 7/28/2014
EOF
}
#HELP FUNCTION GIVES BACKGROUND ON THIS DRIVER
help_me ()
{
clear
cat .help
exit 0
}
def_opts ()
{
clear
cat .options
}

##CLEAR ALL VARIABLES               
rprt_nm= ; dvc_prt= ; bd_rt= ; rsp_lngth= ; izs_val= ;
dvc_prt= ; frqncy= ; dtr= ; ctr= ; p8n1= ; izs= ; saroad= ; p8n1= ; id_nmbr= ; 
recov=4 ; instance=1 ; equi=4
##INITIALIZE THE BUFFER
return='NOT WRITTEN TO'
##loop through all variables and parameters
##Note that the position of the colon indicates the presence
##of a parameter to collect. The h parameter simply
##returns the help message.

while getopts “hn:r:s:d:b:w:l:C:p:I:R:E:” OPTION
do
case $OPTION in
n)
id_nmbr=$OPTARG
;;
a)
abs_path=$OPTARG
;;
I)
instance=$OPTARG
;;
s)
saroad=$OPTARG
;;
r)
rprt_nm=$OPTARG
;;
d)
dvc_prt=$OPTARG
;;
b)
bd_rt=$OPTARG
;;
w)
wt_rsp1=$OPTARG
;;
l)
rsp_lngth=$OPTARG
;;
C)
ctr_val=$OPTARG
;;
p)
p8n1_val=$OPTARG
;;
R)
recov=$OPTARG
recov=`echo "${recov} - 1" | bc`
;;
E)
equi=$OPTARG
equi=`echo "${equi} - 1" | bc`
;;
h)
help_me
exit 1
;;
*)
usage
exit 1
;;
esac
done
#CHECK FOR THE CRITICAL PARAMETERS, IF THEY ARE NOT PRESENT THEN
#RUN THE USAGE FUNCTION WHICH RETURNS THE INSTRUCTIONS FOR PROPER USAGE
#MUST HAVE r n d b c w l i s
if [ -z "$rprt_nm" ] || [ -z "$dvc_prt" ] || [ -z "$bd_rt" ] ||
[ -z "$wt_rsp1" ] || [ -z "$rsp_lngth" ] || [ -z "$saroad" ]
	then
		{
		usage
		exit
		}
fi
timeout_time=`echo $wt_rsp1 + 2 | bc`
	#INITIALIZED THE RAN VARIABLE. THIS IS USED TO TOGGLE THE TIMER GATE
ran=0
	#TRAP CTRL+C
trap cleanup 1 2 3 6 15
	#SETUP THE OPERATING LOOP
while :
do
second=`date "+%S"`
if [ "$second" -eq "00" ] && [ "$ran" -eq "0" ]; then
	{
	#RUN THE RUNIT FUNCTION
		runit
	#TOGGLE THE RAN VARIABLE SO THAT THE PROCESS WILL NOT REPEAT
	#UNTIL THE BEGINNING OF THE NEXT MINUTE.
		ran=1
	#WAIT FOR COMPLETION OF THE RUNIT FUNCTION AND THE VARIABLE
	#SET
		sleep 1
	}
	#AND WAIT ONE SECOND BEFORE CHECKING AGAIN
else 
	{
	#WAIT ONE SECOND
		sleep .5
	#SET RAN TO 0
		ran=0
	}
fi
done;
exit;

